package jeeobserver.server;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
import jeeobserver.utilities.MathUtilities;

public abstract class Statistics implements Serializable {

	 public static abstract class Element extends Statistics {

			private int id;

			public Element(int id) {
				 this.id = id;
			}

			public int getId() {
				 return id;
			}
	 }

	 public static abstract class SamplingStatistics extends Statistics implements Serializable {

			private static final long serialVersionUID = 40L;

			//private final int samplingPeriod;

			private final Date date;

			private int count;

			public SamplingStatistics(Date date, int count) {
				 //this.samplingPeriod = samplingPeriod;
				 this.date = date;
				 this.count = count;
			}

			public int compareTo(HttpSessionStatistics statistics) {
				 return this.getDate().compareTo(statistics.getDate());
			}

			void add(int count) {
				 this.count = this.count + count;
			}

//			public int getSamplingPeriod() {
//				 return samplingPeriod;
//			}

			public Date getDate() {
				 return date;
			}

			public int getCount() {
				 return count;
			}
	 }

	 public static class NumberStatistics implements Serializable {

			private static final long serialVersionUID = 40L;

			private double sum;

			public NumberStatistics(double sum) {
				 this.sum = sum;
			}

			public NumberStatistics(NumberStatistics statistics) {
				 this.sum = statistics.getSum();
			}

			void add(NumberStatistics statistics) {

				 if (!Double.isNaN(this.sum) && !Double.isNaN(statistics.getSum())) {
						this.sum = this.sum + statistics.getSum();
				 } else if (!Double.isNaN(statistics.getSum())) {
						this.sum = statistics.getSum();
				 }
			}

			public double getSum() {
				 return sum;
			}
	 }

	 public static final class NumberRangeStatistics extends NumberStatistics implements Serializable {

			private static final long serialVersionUID = 40L;

			private double minimum;

			private double maximum;

			public NumberRangeStatistics(double minimum, double maximum, double sum) {
				 super(sum);

				 this.minimum = minimum;
				 this.maximum = maximum;
			}

			public NumberRangeStatistics(double value) {
				 this(value, value, value);
			}

			public NumberRangeStatistics(NumberRangeStatistics statistics) {
				 super(statistics);
				 this.minimum = statistics.getMinimum();
				 this.maximum = statistics.getMaximum();
			}

			public double getMinimum() {
				 return minimum;
			}

			public double getMaximum() {
				 return maximum;
			}

			void add(NumberRangeStatistics statistics) {

				 super.add(statistics);

				 if (!Double.isNaN(this.minimum) && !Double.isNaN(statistics.getMinimum())) {
						this.minimum = Math.min(this.minimum, statistics.getMinimum());
				 } else if (!Double.isNaN(statistics.getMinimum())) {
						this.minimum = statistics.getMinimum();
				 }

				 if (!Double.isNaN(this.maximum) && !Double.isNaN(statistics.getMaximum())) {
						this.maximum = Math.max(this.maximum, statistics.getMaximum());
				 } else if (!Double.isNaN(statistics.getMaximum())) {
						this.maximum = statistics.getMaximum();
				 }
			}
	 }

	 public static final class NumberTrend implements Serializable {

			private static final long serialVersionUID = 40L;

			private transient Map<Date, TrendValue> statistics;

			private transient double average;

			private transient double sum;

			private transient double count;

			private double averageDeviation;

			private double averageDeviationIndex;

			private double averageMinimumDetected;

			private double averageMaximumDetected;

			private double averageLastDetected;

			private double averageLastPredicted;

			private double sumMaximumDetected;

			private double sumMinimumDetected;

			private double sumLastDetected;

			private double sumLastPredicted;

			private double averageTrendIndex;

			private double sumTrendIndex;

			public NumberTrend() {
				 this.statistics = new TreeMap<Date, TrendValue>();
			}

			void add(Date date, double sum, int count) {
				 if (!Double.isNaN(sum)) {
						if (this.statistics.containsKey(date)) {
							 this.statistics.get(date).add(new TrendValue(sum, count));
						} else {
							 this.statistics.put(date, new TrendValue(sum, count));
						}
				 }
			}

			public void calculateTrendStatistics() {

				 if (this.statistics.isEmpty()) {
						return;
				 }

				 double[][] values = new double[this.statistics.size()][3];

				 int index = 0;
				 Date lastDate = null;

				 Date dateMinimum = null;
				 Date dateMaximum = null;

				 for (Date date : this.statistics.keySet()) {

						if (dateMinimum == null || dateMinimum.after(date)) {
							 dateMinimum = date;
						}
						if (dateMaximum == null || dateMinimum.before(date)) {
							 dateMaximum = date;
						}

						TrendValue value = this.statistics.get(date);

						values[index][0] = date.getTime() - dateMinimum.getTime();


						this.sum = this.sum + value.getSum();

						this.count = this.count + value.getCount();

						values[index][1] = this.sum;
						values[index][2] = value.getSum() / value.getCount();

						if (index == 0) {
							 this.sumMinimumDetected = values[index][1];
							 this.sumMaximumDetected = values[index][1];

							 this.averageMinimumDetected = values[index][2];
							 this.averageMaximumDetected = values[index][2];

							 lastDate = date;
						} else {
							 this.sumMinimumDetected = Math.min(this.sumMinimumDetected, values[index][1]);
							 this.sumMaximumDetected = Math.max(this.sumMaximumDetected, values[index][1]);

							 this.averageMinimumDetected = Math.min(this.averageMinimumDetected, values[index][2]);
							 this.averageMaximumDetected = Math.max(this.averageMaximumDetected, values[index][2]);

							 if (date.after(lastDate)) {
									lastDate = date;

									this.sumLastDetected = values[index][1];
									this.averageLastDetected = values[index][2];
							 }
						}

						index = index + 1;
				 }

				 this.average = this.sum / this.count;

				 this.averageDeviation = MathUtilities.deviation(values, 2);
				 this.averageDeviationIndex = MathUtilities.trendIndex(this.average, this.average + this.averageDeviation);

				 this.sumLastPredicted = MathUtilities.linearRegression(values, 1, dateMaximum.getTime() - dateMinimum.getTime());
				 this.averageLastPredicted = MathUtilities.linearRegression(values, 2, dateMaximum.getTime() - dateMinimum.getTime());

				 this.averageTrendIndex = MathUtilities.trendIndex(this.average, this.averageLastPredicted);
				 this.sumTrendIndex = MathUtilities.trendIndex(this.sum, this.sumLastPredicted);

				 this.statistics.clear();
			}

			public double getAverageDeviation() {
				 return averageDeviation;
			}

			public double getAverageDeviationIndex() {
				 return averageDeviationIndex;
			}

			public double getAverageMinimumDetected() {
				 return averageMinimumDetected;
			}

			public double getAverageMaximumDetected() {
				 return averageMaximumDetected;
			}

			public double getAverageLastDetected() {
				 return averageLastDetected;
			}

			public double getAverageLastPredicted() {
				 return averageLastPredicted;
			}

			public double getSumMaximumDetected() {
				 return sumMaximumDetected;
			}

			public double getSumMinimumDetected() {
				 return sumMinimumDetected;
			}

			public double getSumLastDetected() {
				 return sumLastDetected;
			}

			public double getSumLastPredicted() {
				 return sumLastPredicted;
			}

			public double getAverageTrendIndex() {
				 return averageTrendIndex;
			}

			public double getSumTrendIndex() {
				 return sumTrendIndex;
			}

			private static final class TrendValue {

				 private double sum;

				 private int count;

				 TrendValue(double sum, int count) {
						this.sum = sum;
						this.count = count;
				 }

				 public void add(TrendValue trendValue) {
						this.sum = this.sum + trendValue.getSum();
						this.count = this.count + trendValue.getCount();
				 }

				 public double getSum() {
						return sum;
				 }

				 public int getCount() {
						return count;
				 }
			}
	 }

	 public static class DateStatistics implements Serializable {

			private static final long serialVersionUID = 40L;

			private Date minimum;

			private Date maximum;

			public DateStatistics(Date minimum, Date maximum) {
				 this.minimum = minimum;
				 this.maximum = maximum;
			}

			public DateStatistics(Date date) {
				 this.minimum = date;
				 this.maximum = date;
			}

			public DateStatistics(DateStatistics statistics) {
				 this.minimum = statistics.getMinimum();
				 this.maximum = statistics.getMaximum();
			}

			void add(DateStatistics statistics) {
				 this.minimum = MathUtilities.min(this.minimum, statistics.getMinimum());
				 this.maximum = MathUtilities.max(this.maximum, statistics.getMaximum());
			}

			public Date getMinimum() {
				 return minimum;
			}

			public void setMinimum(Date minimum) {
				 this.minimum = minimum;
			}

			public Date getMaximum() {
				 return maximum;
			}

			public void setMaximum(Date maximum) {
				 this.maximum = maximum;
			}
	 }
}
